/*	$Id: conf.y 501 2007-08-30 03:36:03Z phrakt $	*/
/*
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

%{
#include <sys/param.h>

#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>

#include <canopy/mem.h>
#include <canopy/queue.h>
#include <canopy/string.h>

#include "log.h"
#include "conf.h"
#include "private.h"


/* configuration variable */
struct cnp_conf_var {
	char   *cv_name;
	char   *cv_val;
	int     cv_type;
	size_t  cv_len;

	TAILQ_ENTRY(cnp_conf_var) cv_link;
};


/* configuration directive information */
struct cnp_conf_dir {
	char  *cd_name;
	int    cd_minargc;
	int    cd_maxargc;
	int  (*cd_hdlr)(int, char **);

	TAILQ_ENTRY(cnp_conf_dir) cd_link;
};



/* reserved characters */
static const char conf_reserved[] = "=${}[]";


/* parser states */
#define CFP_STATE_INIT     0
#define CFP_STATE_PARSING  1
#define CFP_STATE_DONE     2
#define CFP_STATE_ERROR    3
#define CFP_STATE_FUBAR    10



#define CFP_BGETC(p, c)					\
	do {						\
		if ((p)->cf_bused == 0) 		\
			break;				\
		c = *((p)->cf_bdata++);			\
		(p)->cf_bused--;			\
	} while (0)


#define CFP_BUNGETC(p, c)				\
	do {						\
		if ((p)->cf_bdata == (p)->cf_buf) 	\
			break;				\
		*(--(p)->cf_bdata) = (char)(c);		\
		(p)->cf_bused++;			\
	} while (0)


#define CFP_SETERR(p, e)				\
	do {						\
		(p)->cf_state = CFP_STATE_ERROR;	\
		(p)->cf_error = (e);			\
	} while (0)

struct cnp_conf {
	int     cf_flags;
	int     cf_state;
	int     cf_error;			/* error code, if any */

	char   *cf_file;			/* file being parsed, if any */
	FILE   *cf_fin;
	int     cf_lineno;			/* current line number */

	char   *cf_buf;
	char   *cf_bdata;			/* start of data in buffer */
	size_t  cf_blen;
	size_t  cf_bused;			/* bytes used in buffer */

	TAILQ_HEAD(, cnp_conf_var) cf_vars;		/* variables */
	TAILQ_HEAD(, cnp_conf_dir) cf_dirs;		/* directives */

	/* argument vector for directives */
	char   *cf_argv[CONF_DIR_MAXARG];
	int     cf_argc;
};


struct yystype {
	union {
		uint64_t          num;
		char             *str;
		uint8_t          *bstr;
		struct cnp_conf_dir  *dir;
	} v;
	size_t  vlen;
	u_int   type;

	int lineno;
};


#define YYSTYPE struct yystype


/*
 * In order to have a reentrant parser, we have to use a big lock model
 */
static cnp_conf_t *curpsr = NULL;	/* pointer to current parser */

#ifdef THREAD_SAFE
static pthread_mutex_t cfp_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif



static int                  cnp_conf_getc       (cnp_conf_t *);
static int                  cnp_conf_ungetc     (cnp_conf_t *, int);
static void                 cnp_conf_log        (enum cnp_log_pri, cnp_conf_t *,
							const char *, ...);
static struct cnp_conf_dir* cnp_conf_finddir    (cnp_conf_t *, const char *);
static int                  cnp_conf_rundir     (cnp_conf_t *);
static struct cnp_conf_var* cnp_conf_newvar     (const char *);
static struct cnp_conf_var* cnp_conf_findvar    (cnp_conf_t *, const char *);
static void                 cnp_conf_freevar    (struct cnp_conf_var *);
static int                  cnp_conf_chkvarname (const char *);


static int  yyerror     (const char *, ...);
static int  yylex       (void);
static int  yylex_var   (void);
#if 0
static int  yylex_qstr  (int);
#endif
static int  yyparse     (void);


%}

%token  <v.str>   STRING
%token  <v.num>   NUMBER
%token  <v.dir>   DIRECTIVE
%%

conf		: /* empty */
		| conf '\n'
		| conf var_assign '\n'
		| conf DIRECTIVE
		{
			curpsr->cf_argv[0] = $2->cd_name;
			curpsr->cf_argc = 1;
		} dir_argv '\n'
		{
			if (cnp_conf_rundir(curpsr) == -1) {
				YYERROR;
			}
		}
		| conf error '\n'
		{
			YYERROR;
			CFP_SETERR(curpsr, CONF_ERR_PARSE);
		}
		;

var_assign	: STRING '=' STRING
		{
			cnp_conf_setvar(curpsr, $1, $3);
			cnp_free($1);
			cnp_free($3);
		}
		;


dir_argv	: /* empty */
		| dir_argv STRING
		{
			if (curpsr->cf_argc == CONF_DIR_MAXARG) {
				YYERROR;
			}

			curpsr->cf_argv[curpsr->cf_argc++] = $2;
		}

%%


/*
 * cnp_conf_getc()
 *
 * Retrieve the next available character from the stream being parsed by
 * <cp>.
 */
static int
cnp_conf_getc(cnp_conf_t *cp)
{
	int c;

	/* check if we've got something in the parse buffer first */
	if (cp->cf_bused > 0) {
		CFP_BGETC(cp, c);
		return (c);
	}

	c = getc(cp->cf_fin);

	return (c);
}


/*
 * cnp_conf_ungetc()
 *
 * Push back the character <c> in the parser <cp>'s parse buffer.  We do not
 * rely on ungetc() because it guarantees only one character pushback and,
 * although this mostly what we need, some cases may arise where we need
 * more pushback guaranteed.
 */
static int
cnp_conf_ungetc(cnp_conf_t *cp, int c)
{
	CFP_BUNGETC(cp, c);

	return (0);
}


/*
 * cnp_conf_alloc()
 *
 * Allocate a new configuration parser and initialize its internals for the
 * parsing of files which have the format specified in <style>.
 * Returns a pointer to the new parser on success, or NULL on failure.
 */
cnp_conf_t*
cnp_conf_alloc(int flags)
{
	cnp_conf_t *cp;

	if ((cp = cnp_malloc(sizeof(*cp))) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERRNO,
		    "failed to allocate configuration object");
		return (NULL);
	}
	memset(cp, 0, sizeof(*cp));

	cp->cf_flags = flags;
	cp->cf_lineno = 1;

	cp->cf_file = NULL;
	cp->cf_fin = NULL;

	TAILQ_INIT(&(cp->cf_vars));
	TAILQ_INIT(&(cp->cf_dirs));

	cp->cf_blen = CONF_BUF_DEFSIZE;
	if ((cp->cf_buf = cnp_malloc(cp->cf_blen)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERRNO,
		    "failed to allocate configuration parse buffer");
		cnp_conf_free(cp);
		return (NULL);
	}

	cp->cf_bdata = cp->cf_buf + (cp->cf_blen / 2);
	cp->cf_bused = 0;

	cp->cf_argc = 0;
	memset(cp->cf_argv, 0, sizeof(cp->cf_argv));

	return (cp);
}


/*
 * cnp_conf_free()
 *
 * Free all resources currently used by the parser <cp>.  Any further
 * operation on this parser will have undefined behaviour.
 */
void
cnp_conf_free(cnp_conf_t *cp)
{
	struct cnp_conf_var *var;
	struct cnp_conf_dir *dir;

	if (cp != NULL) {
		if (cp->cf_file != NULL)
			cnp_free(cp->cf_file);

		if (cp->cf_fin != NULL)
			(void)fclose(cp->cf_fin);

		if (cp->cf_buf != NULL)
			cnp_free(cp->cf_buf);

		while (!TAILQ_EMPTY(&cp->cf_vars)) {
			var = TAILQ_FIRST(&cp->cf_vars);
			TAILQ_REMOVE(&cp->cf_vars, var, cv_link);
			cnp_conf_freevar(var);
		}

		while (!TAILQ_EMPTY(&cp->cf_dirs)) {
			dir = TAILQ_FIRST(&cp->cf_dirs);
			TAILQ_REMOVE(&cp->cf_dirs, dir, cd_link);
			cnp_free(dir->cd_name);
			cnp_free(dir);
		}

		cnp_free(cp);
	}
}

/*
 * cnp_conf_reset()
 *
 * Reset the parser's internal states.  Once this operation has been performed,
 * the parser should be ready to parser a new configuration file.
 */
void
cnp_conf_reset(cnp_conf_t *cp)
{
	cp->cf_state = CFP_STATE_INIT;
	cp->cf_error = CONF_ERR_NOERR;
	cp->cf_lineno = 1;

	if (cp->cf_file != NULL) {
		cnp_free(cp->cf_file);
		cp->cf_file = NULL;
	}
}

/*
 * cnp_conf_getfl()
 *
 * Returns the configuration parser's flags.
 */
int
cnp_conf_getfl(cnp_conf_t *cp)
{
	return (cp->cf_flags);
}

/*
 * cnp_conf_setfl()
 *
 * Set the parser's flags to <flags>.
 */
int
cnp_conf_setfl(cnp_conf_t *cp, int flags)
{
	cp->cf_flags = flags;
	return (0);
}

/*
 * cnp_conf_parse()
 *
 * Parse the contents of the configuration file <conf>.
 * Returns 0 on success, or -1 on error.
 */
int
cnp_conf_parse(cnp_conf_t *cp, const char *conf)
{
	int ret = 0;

	cnp_log(cnp_logchan, CNP_LOG_DEBUG,
	    "starting parsing of configuration file %s", conf);
	if ((cp->cf_file = strdup(conf)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR, "failed to copy path");
		CFP_SETERR(cp, CONF_ERR_NOMEM);
		return (-1);
	}

	if ((cp->cf_fin = fopen(conf, "r")) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERRNO, "failed to open %s", conf);
		return (-1);
	}

#ifdef THREAD_SAFE
	if (pthread_mutex_lock(&cfp_mutex) != 0)
		return (-1);
#endif

	/* set ourself as the current parser */
	curpsr = cp;
	cnp_log(cnp_logchan, CNP_LOG_DEBUG, "calling yyparse()");
	if (yyparse() != 0)
		ret = -1;

#ifdef THREAD_SAFE
	(void)pthread_mutex_unlock(&cfp_mutex);
#endif

	(void)fclose(cp->cf_fin);
	cp->cf_fin = NULL;

	return (ret);
}

/*
 * cnp_conf_getvar()
 *
 * Get a macro <macro>'s associated value.  Returns the value string on
 * success, or NULL if no such macro exists.
 */
int
cnp_conf_getvar(cnp_conf_t *cp, const char *name, char *dst, size_t len)
{
	struct cnp_conf_var *cvp;

	TAILQ_FOREACH(cvp, &(cp->cf_vars), cv_link)
		if (strcmp(cvp->cv_name, name) == 0)
			return (cvp->cv_type);

	return (-1);
}

/*
 * cnp_conf_setvar()
 *
 */
int
cnp_conf_setvar(cnp_conf_t *cp, const char *name, const char *val)
{
	int ret;
	struct cnp_conf_var *cvp;

	/* validate arguments */
	if (!cnp_conf_chkvarname(name)) {
		cnp_conf_log(CNP_LOG_ERR, cp, "invalid variable name `%s'",
		    name);
		CFP_SETERR(cp, CONF_ERR_INVARG);
		return (-1);
	}

	if ((cvp = cnp_conf_findvar(cp, name)) == NULL) {
		if (cp->cf_flags & CONF_STRICTVAR) {
			cnp_conf_log(CNP_LOG_ERR, cp,
			    "assignment to undeclared variable `%s' "
			    "while in strict variable mode", name);
			/* no setting without prior declaration */
			CFP_SETERR(cp, CONF_ERR_STRICT);
			return (-1);
		}

		if ((cvp = cnp_conf_newvar(name)) == NULL) {
			CFP_SETERR(cp, CONF_ERR_NOMEM);
			return (-1);
		}

		TAILQ_INSERT_TAIL(&(cp->cf_vars), cvp, cv_link);
	}

	if (cvp->cv_val != NULL)
		cnp_free(cvp->cv_val);

	cnp_conf_log(CNP_LOG_DEBUG, cp, "setting %s to `%s'", cvp->cv_name,
	    val);

	ret = 0;
	if ((cvp->cv_val = strdup(val)) == NULL) {
		CFP_SETERR(cp, CONF_ERR_NOMEM);
		ret = -1;
	} else
		cvp->cv_len = strlen(cvp->cv_val);

	return (ret);
}


/*
 * cnp_conf_newvar()
 *
 * Allocate a new data structure to store a config variable with name <name>,
 * and of type <type> (no checking is performed to validate the type passed
 * as argument).
 */
static struct cnp_conf_var*
cnp_conf_newvar(const char *name)
{
	struct cnp_conf_var *cvp;

	if ((cvp = cnp_malloc(sizeof(*cvp))) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to allocate configuration variable");
		return (NULL);
	}

	if ((cvp->cv_name = strdup(name)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to allocate configuration variable");
		cnp_free(cvp);
		return (NULL);
	}

	cvp->cv_val = NULL;

	return (cvp);
}

/*
 * cnp_conf_findvar()
 *
 * Look for the variable whose name is <name> in the context of the parser
 * <cp> and return a pointer to its structure if found, or NULL otherwise.
 */
static struct cnp_conf_var *
cnp_conf_findvar(cnp_conf_t *cp, const char *name)
{
	struct cnp_conf_var *cvp;

	TAILQ_FOREACH(cvp, &(cp->cf_vars), cv_link)
		if (strcmp(cvp->cv_name, name) == 0)
			return (cvp);

	return (NULL);
}

/*
 * cnp_conf_freevar()
 *
 */
static void
cnp_conf_freevar(struct cnp_conf_var *cv)
{
	if (cv->cv_val != NULL)
		cnp_free(cv->cv_val);

	cnp_free(cv->cv_name);
	cnp_free(cv);
}


#if 0
/*
 * cnp_conf_expand()
 *
 */
int
cnp_conf_expand(cnp_conf_t *cf, const char *orig, char *buf, size_t len)
{
	int recurse;
	size_t l = 0, vl;
	char vbuf[CONF_VAR_MAXNAMELEN], tmp[CONF_VAR_MAXNAMELEN];
	const char *ch;
	struct cnp_conf_var *var;

	memset(buf, 0, len);

	for (ch = orig; *ch != '\0'; ch++) {
		if (*ch != '$') {
			buf[l++] = *ch;
		}
		else {
			vl = 0;

			ch++;
			if (*ch == '{') {
				recurse = 0;

				ch++;
				while ((*ch != '}') && (*ch != '\0')) {
					if (*ch == '$')
						recurse = 1;
					vbuf[vl++] = *(ch++);
				}

				if (*ch == '\0') {
					return (-1);
				}

				vbuf[vl] = '\0';

				if (recurse) {
					strlcpy(tmp, vbuf, sizeof(tmp));
					cnp_conf_expand(tmp, vbuf, sizeof(vbuf));
				}
			}
			else {
				while (isalnum(*ch) || (*ch == '.') ||
				    (*ch == '_'))
					vbuf[vl++] = *(ch++);

				ch--;

				vbuf[vl] = '\0';
			}

			var = cnp_conf_findvar(cf, vbuf);
			if (var == NULL) {
				if (cf->cf_flags & CONF_STRICTVAR) {
					/* XXX: blow up */
					return (-1);
				}

				/* nothing to append */
				continue;
			}

			if (var->cv_type == CONF_VAR_STRING)
				l = strlcat(buf, var->cv_val.str, len);
		}
	}

}
#endif

/*
 * cnp_conf_chkvarname()
 *
 * Validate the proposed variable name <name>.
 * Returns 1 if the name is OK, 0 if it doesn't conform to the variable
 * naming convention.
 */
static int
cnp_conf_chkvarname(const char *name)
{
	const char *np;

	if (!isalpha(*name) && (*name != '_'))
		return (0);

	for (np = name + 1; *np != '\0'; np++)
		if (!isalnum(*name) && (*name != '_'))
			return (0);

	return (1);
}

/*
 * cnp_conf_setdir()
 *
 */
int
cnp_conf_setdir(cnp_conf_t *cp, const char *name, int (*hdlr)(int, char **), int min,
    int max)
{
	struct cnp_conf_dir *cdp;

	if ((cdp = cnp_malloc(sizeof(*cdp))) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to allocate configuration directive");
		return (-1);
	}

	if ((cdp->cd_name = strdup(name)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to allocate configuration directive");
		cnp_free(cdp);
		return (-1);
	}

	cdp->cd_hdlr = hdlr;
	cdp->cd_minargc = min;
	cdp->cd_maxargc = max;

	TAILQ_INSERT_TAIL(&(cp->cf_dirs), cdp, cd_link);

	return (0);
}

/*
 * cnp_conf_finddir()
 *
 * Look for the directive whose name is <name> in the context of the parser
 * <cp> and return a pointer to its structure if found, or NULL otherwise.
 */
static struct cnp_conf_dir *
cnp_conf_finddir(cnp_conf_t *cp, const char *name)
{
	struct cnp_conf_dir *cdp;

	TAILQ_FOREACH(cdp, &(cp->cf_dirs), cd_link)
		if (strcmp(cdp->cd_name, name) == 0)
			return (cdp);

	return (NULL);
}

/*
 * cnp_conf_rundir()
 *
 * Run the directive specified in the first entry of the directive argument
 * vector, along with the other arguments supplied.  After the directive was
 * run, the argument vector is reinitialized to prepare for the next call.
 * Returns the directive's return value on success, or -1 on failure.
 */
static int
cnp_conf_rundir(cnp_conf_t *cf)
{
	int i, ret;
	struct cnp_conf_dir *cd;

	cd = cnp_conf_finddir(cf, cf->cf_argv[0]);
	if (cd == NULL) {
		cnp_conf_log(CNP_LOG_ERR, cf, "directive `%s' not found",
		    cd->cd_name);
		ret = -1;
	}
	else if ((cd->cd_minargc > 0) && (cf->cf_argc < cd->cd_minargc)) {
		cnp_conf_log(CNP_LOG_ERR, cf,
		    "not enough arguments to `%s' directive", cd->cd_name);
		ret = -1;
	}
	else if ((cd->cd_maxargc >= 0) && (cf->cf_argc > cd->cd_maxargc)) {
		cnp_conf_log(CNP_LOG_ERR, cf,
		    "too many arguments to `%s' directive", cd->cd_name);
		ret = -1;
	}
	else {
		cnp_conf_log(CNP_LOG_DEBUG, cf, "running directive `%s'",
		    cd->cd_name);
		ret = (*cd->cd_hdlr)(cf->cf_argc, cf->cf_argv);
		if (ret < 0)
			cnp_conf_log(CNP_LOG_WARN, cf,
			    "directive `%s' returned %d", cd->cd_name, ret);
	}

	/* reset the argument vector */
	for (i = 1; i < cf->cf_argc; i++) {
		cnp_free(cf->cf_argv[i]);
		cf->cf_argv[i] = NULL;
	}

	cf->cf_argc = 0;

	return (ret);
}

/*
 * cnp_conf_log()
 *
 */
void
cnp_conf_log(enum cnp_log_pri level, cnp_conf_t *cp, const char *fmt, ...)
{
	char *nfmt;
	va_list vap;

	asprintf(&nfmt, "%s[%u]: %s", cp->cf_file, cp->cf_lineno, fmt);

	va_start(vap, fmt);
	cnp_vlog(cnp_logchan, level, nfmt, vap);
	va_end(vap);
	cnp_free(nfmt);
}

/*
 * yylex()
 *
 * Our own lexer.
 */
static int
yylex(void)
{
	int c;
	char buf[CONF_TOK_MAXLEN], *bp, *ep;

	buf[0] = '\0';
	buf[1] = '\0';

	bp = buf;
	ep = buf + sizeof(buf) - 1;

	yylval.lineno = curpsr->cf_lineno;

	/* skip whitespace */
	while (((c = cnp_conf_getc(curpsr)) == ' ') || (c == '\t'))
		;

	if (c == '#') {
		/* strip comment up to end of line */
		do {
			c = cnp_conf_getc(curpsr);
		} while ((c != '\n') && (c != EOF));
	}

	if (c == EOF)
		c = 0;
	else if (c == '\n')
		yylval.lineno = curpsr->cf_lineno++;
	else if ((c == '=') || (c == '[') || (c == ']')) {
		buf[0] = c;		/* nothing */
	} else if ((c == '"') || (c == '\'')) {
	}
	else {
		/* string */
		do {
			if (c == '$') {
				if (yylex_var() == -1)
					return (-1);
			}
			else {
				*bp++ = (char)c;
				if (bp == ep) {
					CFP_SETERR(curpsr, CONF_ERR_TOKLEN);
					return (-1);
				}
			}

			c = cnp_conf_getc(curpsr);
		} while ((c != EOF) && !isblank(c) && (c != '\n') &&
		    (c != '='));
		cnp_conf_ungetc(curpsr, c);
		*bp = '\0';

		if ((yylval.v.dir = cnp_conf_finddir(curpsr, buf)) != NULL)
			c = DIRECTIVE;
		else {
			c = STRING;
			if ((yylval.v.str = strdup(buf)) == NULL)
				return (-1);
		}
	}

#if 0
	printf("token[%d] = `%s'\n", c, buf);
#endif

	return (c);
}

/*
 * yylex_var()
 *
 */
static int
yylex_var(void)
{
	int c, brkt;
	size_t len;
	char varbuf[128], *bp;
	struct cnp_conf_var *var;

	bp = varbuf;

	c = cnp_conf_getc(curpsr);
	brkt = (c == '{') ? 1 : 0;
	if (brkt)
		c = cnp_conf_getc(curpsr);

	do {
		if (brkt && (c == '}'))
			break;

		*bp++ = (char)c;
		if (bp == (varbuf + sizeof(varbuf))) {
			CFP_SETERR(curpsr, CONF_ERR_BADNAME);
			return (-1);
		}
		c = cnp_conf_getc(curpsr);
	} while (isalnum(c) || (c == '_') || (c == '.'));
	if (!brkt)
		cnp_conf_ungetc(curpsr, c);
	*bp = '\0';

	if ((var = cnp_conf_findvar(curpsr, varbuf)) == NULL) {
		if (curpsr->cf_flags & CONF_STRICTVAR) {
			CFP_SETERR(curpsr, CONF_ERR_STRICT);
			return (-1);
		}
	}
	else {
		len = strlcpy(curpsr->cf_buf, var->cv_val, curpsr->cf_blen);
		curpsr->cf_bdata = curpsr->cf_buf;
		curpsr->cf_bused = len;
	}

	return (0);
}

#if 0
/*
 * yylex_qstr()
 *
 * Take care of quoted strings.  The <quote> argument tells us what kind of
 * quote opened the string.
 */
static int
yylex_qstr(int quote)
{
	int c;
	char buf[10];
	char *bp, *ep;

	while ((c = cnp_conf_getc(curpsr)) != quote) {
		if (quote == '"') {
			if (c == '\\') {
				c = cnp_conf_getc(curpsr);
				switch (c) {
				case 'n':
					c = '\n';
					break;
				case 'r':
					c = '\r';
					break;
				case 't':
					c = '\t';
					break;
				default:
					break;
				}
			}
		}

		*bp++ = (char)c;
		if (bp == ep) {
			CFP_SETERR(curpsr, CONF_ERR_TOKLEN);
			return (-1);
		}
	}
	*bp = '\0';
	if ((yylval.v.str = strdup(buf)) == NULL) {
		CFP_SETERR(curpsr, CONF_ERR_NOMEM);
		return (-1);
	}
	c = STRING;

	return (c);
}
#endif

/*
 * yyerror()
 *
 * Our own hacked up yyerror() that accepts a printf(3)-compatible format
 * string (and subsequent arguments) instead of a regular string.
 */
static int
yyerror(const char *fmt, ...)
{
	char *nfmt;
	va_list vap;

	va_start(vap, fmt);

	if (asprintf(&nfmt, "%s:%d: %s", curpsr->cf_file,
	    yylval.lineno, fmt) == -1) {
		return (-1);
	}
	cnp_vlog(cnp_logchan, CNP_LOG_ERR, nfmt, vap);

	cnp_free(nfmt);
	va_end(vap);
	return (0);

}
